﻿namespace EFS.RC.CC.Tools.Hunters
{
	/// <summary>
	/// Agent hunter
	/// </summary>
	internal class Agent : EFS.Common.Async.AsyncRoot
	{
		#region Composition

		/// Sync access
		private object syncAccess = new object();

		// Customer session
		private EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession;

		// Atempt to allocate operatorPresence
		private EFS.RC.CC.Sessions.OfficeService.Operators.HuntQueue.DialIn operatorSession = null;

		// Ignored list
		private System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.OfficeService.Agent> ignoredAgents = new System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.OfficeService.Agent>();

		// Queue
		private EFS.RC.CC.Tools.Hunters.Queue queue = null;

		// Created 
		private System.DateTime createdOn = System.DateTime.Now;
 
		/// <summary>
		/// Status
		/// </summary>
		public enum HuntStatus { Idle, Allocating, Busy, Accepted, Refused, Dropped }

		/// <summary>
		/// 
		/// </summary>
		public HuntStatus Status { get; private set;} 

		#endregion

		#region Delegates

		/// <summary>
		/// Asynk terminate 
		/// </summary>
		public EFS.Common.Async.AsyncDelegate<EFS.RC.CC.Tools.Hunters.Agent> AsinkTerminatedDelegate { get; private set; }

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="agentEndPoint"></param>
		/// <param name="customerEndPoint"></param>
		internal Agent(EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession, EFS.RC.CC.Tools.Hunters.Queue queue)
		{
			// check
			EFS.Common.Verify.Reference(customerSession, "customerSession");
			EFS.Common.Verify.Reference(queue, "queue");

			// delegate
			AsinkTerminatedDelegate = new EFS.Common.Async.AsyncDelegate<Agent>(this); 

			// set
			this.customerSession = customerSession;
			this.queue = queue;

			// register for shut down
			this.customerSession.AsinkTerminatedDelegate.Register(OnCustomerSessionTerminated);

			// make busy scheduler
			EFS.RC.CC.Tools.Schedulers.OfficeBusy officeBusy = new Schedulers.OfficeBusy(this);
			officeBusy.Start();

			// register
			queue.Register(this);
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Tools.Hunters.Agent [{0}]", customerSession.Describe());
		}

		/// <summary>
		/// When this hunt was created
		/// </summary>
		public System.DateTime CreatedOn
		{
			get { return createdOn; }
		}

		// Customer session
		internal EFS.RC.CC.Sessions.OfficeService.Customers.Customer CustomerSession
		{
			get { return customerSession; }
		}

		/// <summary>
		/// Get allocating session
		/// </summary>
		internal EFS.RC.CC.Sessions.OfficeService.Operators.Operator OperatorSession
		{
			get { return operatorSession; }
		}

		/// <summary>
		/// Clear ignore list
		/// </summary>
		internal void ClearIgnore()
		{
			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// add to ignored
				ignoredAgents.Clear();
			}
		}

		/// <summary>
		/// Busy
		/// </summary>
		internal void Busy()
		{
			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "Busy", this); 

			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check if idle, allocating or refused
				if (Status == HuntStatus.Idle || Status == HuntStatus.Allocating || Status == HuntStatus.Refused)
				{
					// start busy message
					BeginAsyncProcess2(StartBusyMessage);

					// stop ession if any
					BeginAsyncProcess2(StopOperatorSession);
				}
			}
		}

		/// <summary>
		/// Clear ignore list
		/// </summary>
		internal System.Boolean HasIgnoredAgents()
		{
			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// add to ignored
				return ignoredAgents.Count > 0;
			}
		}

		/// <summary>
		/// Allocate operatorPresence
		/// </summary>
		/// <param name="operatorPresence"></param>
		internal System.Boolean TryAllocate(EFS.RC.CC.Presences.Operators.OfficeService.Agent operatorPresence)
		{
			// check
			EFS.Common.Verify.Reference(operatorPresence, "operatorPresence");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "TryAllocate", operatorPresence.User.SIP, this); 

			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (HuntStatus.Idle == Status || HuntStatus.Refused == Status)
				{
					// check ignored
					if (!ignoredAgents.Contains(operatorPresence))
					{
						try
						{
							// al
							Status = HuntStatus.Allocating;

							// new session
							operatorSession = new EFS.RC.CC.Sessions.OfficeService.Operators.HuntQueue.DialIn(operatorPresence, customerSession);

							// register delegates
							operatorSession.AsinkEstablishedDelegate.Register(OnOperatorSessionEstablished);
							operatorSession.AsinkTerminatedDelegate.Register(OnOperatorSessionTerminated);

							// make session
							operatorSession.BeginStartup();

							// all ok
							return true;
						}
						catch (System.Exception ex)
						{
							// log
							EFS.EventLog.Log.LogException(ex, this);

							// idle
							Status = HuntStatus.Idle;

							// set
							this.operatorSession = null;
						}
					}
				}
			}

			// not ok
			return false;
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Agent session terminated
		/// </summary>
		private void OnOperatorSessionTerminated(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnSessionTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// cast
			EFS.RC.CC.Sessions.OfficeService.Operators.HuntQueue.DialIn operatorSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Operators.HuntQueue.DialIn;

			// check
			EFS.Common.Verify.Reference(operatorSession, "operatorSession");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnOperatorSessionTerminated", operatorSession.OperatorPresence.User.SIP, this);

			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (Status == HuntStatus.Allocating)
				{
					// check
					if (!operatorSession.Equals(this.operatorSession))
					{
						throw new System.InvalidOperationException("Terminated Operator Session is different while Allocating");
					}

					// add to ignored
					this.ignoredAgents.Add(operatorSession.OperatorPresence as EFS.RC.CC.Presences.Operators.OfficeService.Agent);

					// declined
					if (operatorSession.DialStauts == Sessions.OfficeService.Operators.HuntQueue.DialIn.DialHuntStatus.Declined)
					{
						// sal
						EFS.RC.CC.Tools.Loggers.CHL.Add(CustomerSession.ConversationGuid, null, null, this.operatorSession.OperatorPresence.User.ObjectGuid, null, null, null, null, EFS.RC.CC.Events.Conversation.Agent_Hunt_Declined);
					}
					else
					{
						// sal
						EFS.RC.CC.Tools.Loggers.CHL.Add(CustomerSession.ConversationGuid, null, null, this.operatorSession.OperatorPresence.User.ObjectGuid, null, null, null, null, EFS.RC.CC.Events.Conversation.Agent_Hunt_NotAnswered);
					}

					// remove
					this.operatorSession = null;

					// rejected
					Status = HuntStatus.Refused;

					// re-register
					queue.Register(this);
				}
			}
		}

		/// <summary>
		/// Agent session terminated
		/// </summary>
		private void OnOperatorSessionEstablished(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnSessionTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// cast
			EFS.RC.CC.Sessions.OfficeService.Operators.Operator operatorSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Operators.Operator;

			// check
			EFS.Common.Verify.Reference(operatorSession, "operatorSession");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnOperatorSessionEstablished", operatorSession.OperatorPresence.User.SIP, this); 

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check if not already terminated from whatever reason
				if (Status == HuntStatus.Allocating)
				{
					// check
					if (!operatorSession.Equals(this.operatorSession))
					{
						throw new System.InvalidOperationException("Terminated Operator Session is different while Allocating");
					}

					// remove terminate delegates
					this.operatorSession.AsinkTerminatedDelegate.UnRegister(OnOperatorSessionTerminated);
					this.customerSession.AsinkTerminatedDelegate.UnRegister(OnCustomerSessionTerminated);

					// allocated
					EFS.RC.CC.Tools.Loggers.CHL.Add(CustomerSession.ConversationGuid, null, null, this.operatorSession.OperatorPresence.User.ObjectGuid, null, null, null, null, EFS.RC.CC.Events.Conversation.Agent_Hunt_Accepted);

					// remove
					this.operatorSession = null;

					// all ok
					Status = HuntStatus.Accepted;
				}
			}

			// run terminated
			AsinkTerminatedDelegate.Start();
		}

		/// <summary>
		/// allocated event
		/// </summary>
		/// <param name="operatorPresence"></param>
		private void OnCustomerSessionTerminated(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnCustomerSessionTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// cast
			EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Customers.Customer;

			// check
			EFS.Common.Verify.Reference(customerSession, "customerSession");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(customerSession.ConversationGuid, "OnCustomerSessionTerminated", this); 

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check customer session
				if (!this.customerSession.Equals(customerSession))
				{
					throw new System.InvalidOperationException("OnCustomerSessionTerminated CustomerSession is different");
				}

				// check if idle, allocating or refused
				if (Status == HuntStatus.Idle || Status == HuntStatus.Allocating || Status == HuntStatus.Refused)
				{
					// stop session
					BeginAsyncProcess2(StopOperatorSession);

					// log
					EFS.RC.CC.Tools.Loggers.CHL.Add(customerSession.ConversationGuid, null, null, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Agent_Hunt_Dropped);

					// dropped
					Status = HuntStatus.Dropped;
				}
			}

			// run terminated
			AsinkTerminatedDelegate.Start();
		}

		/// <summary>
		/// Close operator session
		/// </summary>
		private void StopOperatorSession()
		{
			 // lock
			lock (syncAccess)
			{
				// check presence
				if (null != operatorSession)
				{
					// shut down operator session dial
					operatorSession.BeginCancelDial();

					// shut down operator session 
					operatorSession.BeginShutdown();
				}
			}
		}

		/// <summary>
		/// Delay start busy message
		/// </summary>
		private void StartBusyMessage()
		{
			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "StartBusyMessage", this);

			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// set status to busy
				Status = HuntStatus.Busy;

				// run terminated
				AsinkTerminatedDelegate.Start();

				// busy
				EFS.RC.CC.Tools.Loggers.CHL.Add(CustomerSession.ConversationGuid, null, null, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Agent_Hunt_Busy);

				// start busy message
				customerSession.BeginStartBusyPlayer();
			}
		}

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// log
			EFS.EventLog.Log.LogException(ex, this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(customerSession.ConversationGuid, "OnException", ex, this);
		}

		#endregion
	}
}
